package org.mathcloud.math.io.mml.content.generic;

import java.util.ArrayList;
import java.util.List;

import org.apache.log4j.Logger;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.Namespace;
import org.mathcloud.math.core.common.factory.OperatorObjectFactory;
import org.mathcloud.math.core.common.operator.BinaryOperator;
import org.mathcloud.math.core.common.operator.EmptyOperator;
import org.mathcloud.math.core.common.operator.NaryOperator;
import org.mathcloud.math.core.common.operator.OperatorObject;
import org.mathcloud.math.core.common.operator.UnaryOperator;
import org.mathcloud.math.core.common.quantity.QuantityObject;
import org.mathcloud.math.core.common.signature.OperatorObjectSignature;
import org.mathcloud.math.core.generic.signature.GenericOperatorObjectSignature;
import org.mathcloud.math.io.mml.content.common.ApplyElementTranslator;
import org.mathcloud.math.io.mml.content.common.ElementFactory;
import org.mathcloud.math.io.utility.generic.GenericXMLUtility;

public class GenericApplyElementTranslator extends GenericElementTranslator 
	implements ApplyElementTranslator
{ 	
	public GenericApplyElementTranslator()
	{
		resultObject = null;
		operatorFactory = null;
		elementFactory = null;
	}
	
	@Override
	public void translate()
	{
		/* before going in deep with processing validation against XML Schema should be done
		 * We presumably will use our own XML Schema based on W3 XML Schema for Content MathML
		 * which provides more clear understanding of content  
		*/
		if (requestElement!=null&&operatorFactory!=null&&elementFactory!=null)
		{
			OperatorObjectSignature sign = getOperatorObjectSignature(requestElement);
			OperatorObject res = operatorFactory.newInstance(sign);
			if (res!=null)
			{
				String condition1 = ".//"+prefix+":apply or .//"+prefix+":cerror";
				String errMessage = "Cannot process APPLY element";
				if (sign.isEmptyOperator())
					translate((EmptyOperator)res,condition1,errMessage);
				else if (sign.isUnaryOperator())
					translate((UnaryOperator)res,condition1,errMessage);
				else if (sign.isBinaryOperator())
					translate((BinaryOperator)res,condition1,errMessage);
				else if (sign.isNaryOperator())
					translate((NaryOperator)res,condition1,errMessage);
			}
		}
	}
	
	private void translate(EmptyOperator arg0, String condition1, String errMessage)
	{
		try
		{
			if (!GenericXMLUtility.contains(requestElement, condition1, null))
			{
				arg0.calculate();				
				if (arg0.getResult()!=null)
					resultElement = elementFactory.getMathMLContent(arg0.getResult(),null);
				else resultElement = getCErrorElement(errMessage);
			}				
		} catch (JDOMException e)
		{
			log.info(e);
		}
	}
	
	private void translate(UnaryOperator arg0, String condition1, String errMessage)
	{
		try
		{
			if (!GenericXMLUtility.contains(requestElement, condition1, null))
			{				 				
				QuantityObject param1 = elementFactory.getQuantityObject((Element)requestElement.getChildren().get(1));
				arg0.setInputParams(param1);
				arg0.calculate();
				if (arg0.getResult()!=null)
				{
					resultObject = arg0;
					resultElement = elementFactory.getMathMLContent(arg0.getResult(),null);
				}
				else resultElement = getCErrorElement(errMessage);
			}			
		} catch (JDOMException e)
		{
			log.info(e);
		}
	}
	
	private void translate(BinaryOperator arg0, String condition1, String errMessage)
	{
		try
		{
			if (!GenericXMLUtility.contains(requestElement, condition1, null))
			{
				QuantityObject param1 = elementFactory.getQuantityObject((Element)requestElement.getChildren().get(1));
				QuantityObject param2 = elementFactory.getQuantityObject((Element)requestElement.getChildren().get(2));
				arg0.setInputParams(param1,param2);
				arg0.calculate();			
				if (arg0.getResult()!=null)
				{
					resultObject = arg0;
					resultElement = elementFactory.getMathMLContent(arg0.getResult(),null);
				}
				else resultElement = getCErrorElement(errMessage);
			}
		} catch (JDOMException e)
		{
			log.info(e);
		}
	}
	
	private void translate(NaryOperator arg0, String condition1, String errMessage)
	{
		try
		{
			if (!GenericXMLUtility.contains(requestElement, condition1, null))
			{
				List<QuantityObject> params = new ArrayList<QuantityObject>();				
				QuantityObject param;
				for (int i=0;i<requestElement.getChildren().size()-1;i++)
				{
					param = elementFactory.getQuantityObject((Element)requestElement.getChildren().get(i+1));
					params.add(param);
				}
				arg0.setInputParams(params);
				arg0.calculate();												
				if (arg0.getResult()!=null)
				{
					resultObject = arg0;
					resultElement = elementFactory.getMathMLContent(arg0.getResult(),null);
				}
				else resultElement = getCErrorElement(errMessage);
			}
		} catch (JDOMException e)
		{
			log.info(e);
		}
	}	
	
	private OperatorObjectSignature getOperatorObjectSignature(Element requestElement)
	{ 
		OperatorObjectSignature res = new GenericOperatorObjectSignature();
		@SuppressWarnings("unchecked")
		List<Element> children = requestElement.getChildren();		
		res.setMMLContentTag(children.get(0).getName());
		res.setOperandsNumber(children.size()-1);		
		return res;
	}

	@Override
	public OperatorObject getResultObject()
	{
		return resultObject;
	}

	@Override
	public boolean validateRequest()
	{
		return false;
	}

	@Override
	public Element getMathMLContent(OperatorObject arg0)
	{
		if (arg0!=null)
		{
			if (arg0.getSignature().isEmptyOperator())
				return getMMLContent((EmptyOperator)arg0);
			else if (arg0.getSignature().isUnaryOperator())
				return getMMLContent((UnaryOperator)arg0);
			else if (arg0.getSignature().isBinaryOperator())
				return getMMLContent((BinaryOperator)arg0);
			else if (arg0.getSignature().isNaryOperator())
				return getMMLContent((NaryOperator)arg0);
			else return null;
		} 
		else return null;
	}

	
	private Element getMMLContent(EmptyOperator arg0)
	{
		if (arg0!=null)
    {
  		Element out = new Element("apply", Namespace.getNamespace(prefix,namespaceURI));
  		out.addContent(new Element(arg0.getSignature().getMMLContentTag(), 
  				Namespace.getNamespace(prefix,namespaceURI)));
  		return out;    	
    }
    else return null;
	}
	
	private Element getMMLContent(UnaryOperator arg0)
	{
    if (arg0!=null)
    {
  		Element out = new Element("apply", Namespace.getNamespace(prefix,namespaceURI));
  		out.addContent(new Element(arg0.getSignature().getMMLContentTag(), 
  				Namespace.getNamespace(prefix,namespaceURI)));
  		
			ElementFactory factory;
			if (elementFactory!=null)
				factory = elementFactory;
			else factory = new GenericElementFactory();
			Element contentEl = factory.getMathMLContent(arg0.getOperand(), null);
			
  		contentEl.setNamespace(out.getNamespace());
  		out.addContent(contentEl);
  		return out;    	
    }
    else return null;
	}
	
	private Element getMMLContent(BinaryOperator arg0)
	{
		if (arg0!=null)
		{
  		Element out = new Element("apply", Namespace.getNamespace(prefix,namespaceURI));
  		out.addContent(new Element(arg0.getSignature().getMMLContentTag(), 
  				Namespace.getNamespace(prefix,namespaceURI)));
			
  		ElementFactory factory;
			if (elementFactory!=null)
				factory = elementFactory;
			else factory = new GenericElementFactory();

  		Element contentEl;
  		for (int i=0;i<2;i++)
  		{
    		contentEl = factory.getMathMLContent(arg0.getOperand(i), null);
    		contentEl.setNamespace(out.getNamespace());
    		out.addContent(contentEl);  			
  		}
  		return out;    	
		}
		else return null;
	}
	
	private Element getMMLContent(NaryOperator arg0)
	{
		if (arg0!=null)
		{
  		Element out = new Element("apply", Namespace.getNamespace(prefix,namespaceURI));  		
  		out.addContent(new Element(arg0.getSignature().getMMLContentTag(), 
  				Namespace.getNamespace(prefix,namespaceURI)));

  		ElementFactory factory;
			if (elementFactory!=null)
				factory = elementFactory;
			else factory = new GenericElementFactory();

  		Element contentEl;  		
  		for (int i=0;i<arg0.getSignature().getOperandsNumber();i++)
  		{
    		contentEl = factory.getMathMLContent(arg0.getOperand(i), null);
    		contentEl.setNamespace(out.getNamespace());
    		out.addContent(contentEl);  			
  		}
  		return out;    	
		}
		else return null;
	}
	
	@Override
	public OperatorObjectFactory getFactory()
	{
		return operatorFactory;
	}

	@Override
	public void setOperatorFactory(OperatorObjectFactory arg0)
	{
		operatorFactory = arg0;
	}

	public ElementFactory getElementFactory()
	{
		return elementFactory;
	}

	@Override
	public void setElementFactory(ElementFactory arg0)
	{
		elementFactory = arg0;		
	}
	
	public static String namespaceURI = "http://www.w3.org/1998/Math/MathML";	
	private OperatorObject resultObject;
	private OperatorObjectFactory operatorFactory;
	private ElementFactory elementFactory;
	

	private static final Logger log = Logger.getLogger(GenericApplyElementTranslator.class);


}
